# 5.进入保护模式 ​ 结合`64-ia-32-architectures-software-developer-manual-325462`的Volume3的`9.9.1 Switching to Protected Mode`与国内的其他操作系统书籍对比,我将进入保护模式分为以下几步: 1. 关闭中断。使用`cli`指令屏蔽外中断 2. 打开A20 3. 加载GDT 4. CR0.PE=1 5. 使用jmp指令刷新流水线 6. 初始化段选择子 其中,第五步一定要紧随在第四步之后。 --- 具体代码实现在loader.asm中,如下: ```nasm ;进入IA-32e准备工作: ;1.进入保护模式 ;2.开启PAE ;3.开启PML4 ;4.设置IA32_EFER.LME = 1 LOADER_BASE equ 0x900 GDT_BASE equ 0x500 org LOADER_BASE LOADER_START: ;现在r8~r15、rax、es、cs、ss、ds、fs、gs、为0 ;下面准备保护模式 ;1.关闭中断 ;2.打开A20 ;3.加载GDT ;4.CR0.PE=1 ;下面将GDT储存到0x500处,每个段描述符4+4个字节 mov bx,GDT_BASE CODE_DESC: mov dword [bx+8],0x0000ffff ;跳过第一个无效段描述符 mov dword [bx+12],0x00cf9800 DATE_STACK_DASC: mov dword [bx+16],0x0000ffff mov dword [bx+20],0x00cf9200 GDT_pointer: mov word [bx+24],23 ;GDT界限为23 mov dword [bx+26],GDT_BASE ;GDT基址 ;下面创建选择子 SELECTOR_CODE equ 1_000b SELECTOR_DATA_STACK equ 10_000b cli ;关闭中断 in al,0x92 or al,0000_0010b out 0x92,al ;打开A20 lgdt [bx+24] ;加载GDT mov eax,cr0 or eax,1 mov cr0,eax ;CR0.PE=1 jmp dword SELECTOR_CODE:Protect_start ;刷新流水线 [bits 32] Protect_start: mov ax,SELECTOR_DATA_STACK mov ds,ax mov es,ax mov fs,ax mov gs,ax mov ss,ax mov esp,LOADER_BASE hlt ;---------------保护模式初始化完成-------------------- ``` 上述代码将CS初始化为代码段选择子(第42行),将es、ss、ds、fs、gs都使用数据-栈选择子初始化。 ## 代码运行 ​ 进入meOS目录,打开`start.cmd`,输入`make`,在打开bochs后进行调试,调试输出如下: > Next at t=0 > (0)\ [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0 > **\ show mode** > show mode switch: ON > show mask is: mode > \ c > 00000362789: switched from 'real mode' to 'protected mode' > 00001713048: switched from 'protected mode' to 'real mode' > 00001713058: switched from 'real mode' to 'protected mode' > 00001901775: switched from 'protected mode' to 'real mode' > **00006122383: switched from 'real mode' to 'protected mode'** > Next at t=2879361142 > (0)\ [0x00000000095b] 0008:000000000000095b (unk. ctxt): add byte ptr ds:[eax], al ; 0000 > **\ sreg** > es:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1 > ​ Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed > cs:0x0008, dh=0x00cf9900, dl=0x0000ffff, valid=1 > ​ Code segment, base=0x00000000, limit=0xffffffff, Execute-Only, Non-Conforming, Accessed, 32-bit > ss:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1 > ​ Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed > ds:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1 > ​ Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed > fs:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1 > ​ Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed > gs:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1 > ​ Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed > ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1 > tr:0x0000, dh=0x00008b00, dl=0x0000ffff, valid=1 > **gdtr:base=0x0000000000000500, limit=0x17** > idtr:base=0x0000000000000000, limit=0x3ff > **\ info gdt** > Global Descriptor Table (base=0x0000000000000500, limit=23): > GDT\[0x00]=??? descriptor hi=0x00000000, lo=0x00000000 > GDT\[0x01]=Code segment, base=0x00000000, limit=0xffffffff, Execute-Only, Non-Conforming, Accessed, 32-bit > GDT\[0x02]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed > You can list individual entries with 'info gdt \[NUM]' or groups with 'info gdt \[NUM]\[NUM]' > \ q > (0).\[2879361142]\[0x00000000095b] 0008:000000000000095b (unk. ctxt): add byte ptr ds:\[eax], al ; 0000 ​ 其中`show mode`用来显示虚拟机模式转换,`sreg`用来显示段寄存器的值,`info gdt`用来显示段描述符的值。 可以看到: 1.cs=0x0008=1_000b,而其余段寄存器的值也与选择子的值相同,说明选择子初始化成功。 2.`gdtr`的值为0x500,段界限为0x17=23,这些与GDT_pointer的值相同,说明GDT初始化成功。 3.gdt中共有3个段描述符,第零个废弃,第一个和第二个也与我们设置的相同,说明段描述符加载正确。 --- ## 至此,保护模式加载完成